package com.permissions.dispatcher.processor.impl;

import static com.permissions.dispatcher.processor.util.Constants.FILE_COMMENT;
import static com.permissions.dispatcher.processor.util.Extensions.simpleString;
import static com.permissions.dispatcher.processor.util.Helpers.*;

import com.permissions.dispatcher.annotation.NeedsPermission;
import com.permissions.dispatcher.processor.JavaProcessorUnit;
import com.permissions.dispatcher.processor.RequestCodeProvider;
import com.permissions.dispatcher.processor.RuntimePermissionsElement;
import com.permissions.dispatcher.processor.util.Extensions;
import com.permissions.dispatcher.processor.util.Helpers;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;

/**
 *  生成java基础方法封装
 */
public abstract class JavaBaseProcessorUnit implements JavaProcessorUnit {
    /**
     * 权限类类名
     */
    protected ClassName permissionUtils;
    private ClassName grantableRequest;
    private String read_media;
    private String write_media;

    private HashMap<String, SensitivePermissionInterface> addWithCheckBodyMap = new HashMap<>();

    public JavaBaseProcessorUnit() {
        permissionUtils = ClassName.get("com.permissions.dispatcher", "PermissionUtils");
        grantableRequest = ClassName.get("com.permissions.dispatcher.annotation", "GrantableRequest");
    }

    @Override
    public JavaFile createFile(RuntimePermissionsElement permissionsElement, RequestCodeProvider requestCodeProvider) {
        return JavaFile.builder(
                permissionsElement.getPackageName(), createFileSpec(permissionsElement, requestCodeProvider))
                .addFileComment(FILE_COMMENT)
                .build();
    }

    abstract void addRequestPermissionsStatement(
            MethodSpec.Builder builder, String targetParam, String permissionField, String requestCodeField);

    abstract void addShouldShowRequestPermissionRationaleCondition(
            MethodSpec.Builder builder, String targetParam, String permissionField, boolean isPositiveCondition);

    abstract String getActivityName(String targetParam);

    private void addShouldShowRequestPermissionRationaleCondition(
            MethodSpec.Builder builder, String targetParam, String permissionField) {
        addShouldShowRequestPermissionRationaleCondition(builder, targetParam, permissionField, true);
    }

    ;

    private TypeSpec createFileSpec(
            RuntimePermissionsElement permissionsElement, RequestCodeProvider requestCodeProvider) {
        return TypeSpec.classBuilder(permissionsElement.getGeneratedClassName())
                .addOriginatingElement(permissionsElement.getElement())
                .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
                .addFields(createFields(permissionsElement, requestCodeProvider))
                .addMethod(createConstructor())
                .addMethods(createWithPermissionCheckMethods(permissionsElement))
                .addMethods(createOnShowRationaleCallbackMethods(permissionsElement))
                .addMethods(createPermissionHandlingMethods(permissionsElement))
                .addTypes(createPermissionRequestClasses(permissionsElement))
                .build();
    }

    private List<FieldSpec> createFields(
            RuntimePermissionsElement permissionsElement, RequestCodeProvider requestCodeProvider) {
        List<FieldSpec> fieldSpecs = new ArrayList<>();
        for (ExecutableElement ele : permissionsElement.getNeedsElements()) {
            fieldSpecs.add(createRequestCodeField(ele, requestCodeProvider.nextRequestCode()));
            fieldSpecs.add(createPermissionField(ele));
            if (!ele.getParameters().isEmpty()) {
                boolean hasOnRationaleParams = true;
                ExecutableElement element = permissionsElement.findOnRationaleForNeeds(ele);
                if (element != null && element.getParameters() != null) {
                    hasOnRationaleParams = !element.getParameters().isEmpty();
                }

                if (hasOnRationaleParams) {
                    fieldSpecs.add(createPendingRequestField(ele));
                } else {
                    fieldSpecs.addAll(createArgFields(ele));
                }
            }
        }

        return fieldSpecs;
    }

    private FieldSpec createRequestCodeField(ExecutableElement executableElement, int nextRequestCode) {
        return FieldSpec.builder(int.class, requestCodeFieldName(executableElement))
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .initializer("$L", nextRequestCode)
                .build();
    }

    private FieldSpec createPermissionField(ExecutableElement executableElement) {
        List<String> permissionValue = Extensions.permissionValue(
                executableElement.getAnnotation(NeedsPermission.class));
        String formattedValue = Extensions.joinToString(permissionValue);
        return FieldSpec.builder(ArrayTypeName.of(String.class), permissionFieldName(executableElement))
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .initializer("$N", "new String[]" + formattedValue)
                .build();
    }

    private FieldSpec createPendingRequestField(ExecutableElement executableElement) {
        return FieldSpec.builder(grantableRequest, pendingRequestFieldName(executableElement))
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
                .build();
    }

    private List<FieldSpec> createArgFields(ExecutableElement executableElement) {
        List<FieldSpec> fieldSpecs = new ArrayList<>();

        for (Element element : executableElement.getParameters()) {
            fieldSpecs.add(
                    FieldSpec.builder(Helpers.typeNameOf(element), argumentFieldName(executableElement, element))
                            .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
                            .build());
        }

        return fieldSpecs;
    }

    private MethodSpec createConstructor() {
        return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
    }

    private List<MethodSpec> createWithPermissionCheckMethods(RuntimePermissionsElement permissionsElement) {
        List<MethodSpec> methodSpecs = new ArrayList<>();
        for (ExecutableElement element : permissionsElement.getNeedsElements()) {
            methodSpecs.add(createWithPermissionCheckMethod(permissionsElement, element));
        }

        return methodSpecs;
    }

    private MethodSpec createWithPermissionCheckMethod(
            RuntimePermissionsElement permissionsElement, ExecutableElement ee) {
        String targetParam = "target";

        MethodSpec.Builder builder =
                MethodSpec.methodBuilder(Helpers.withPermissionCheckMethodName(ee))
                        .addTypeVariables(permissionsElement.getTypeVariables())
                        .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addParameter(ParameterSpec.builder(permissionsElement.getTypeName(), targetParam).build());

        for (Element element : ee.getParameters()) {
            builder.addParameter(Helpers.typeNameOf(element), simpleString(element));
        }
        addWithPermissionCheckBody(builder, ee, permissionsElement, targetParam);
        return builder.build();
    }

    private void addWithPermissionCheckBody(
            MethodSpec.Builder builder,
            ExecutableElement needsMethod,
            RuntimePermissionsElement rpe,
            String targetParam) {
        // Create field names for the constants to use
        String requestCodeField = requestCodeFieldName(needsMethod);
        String permissionField = permissionFieldName(needsMethod);

        // Add the conditional for when permission has already been granted
        String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
        String activityVar = getActivityName(targetParam);
        SensitivePermissionInterface var10000 = this.addWithCheckBodyMap.get(needsPermissionParameter);
        if (var10000 != null) {
            var10000.addHasSelfPermissionsCondition(builder, activityVar, permissionField);
        } else {
            builder.beginControlFlow(
                    "if ($T.hasSelfPermissions($N, $N))", permissionUtils, activityVar, permissionField);
        }
        builder.addCode(
                CodeBlock.builder()
                        .add("$N.$N(", targetParam, simpleString(needsMethod))
                        .add(varargsParametersCodeBlock(needsMethod))
                        .addStatement(")")
                        .build());
        builder.nextControlFlow("else");

        // Add the conditional for "OnShowRationale", if present
        ExecutableElement onRationale = rpe.findOnRationaleForNeeds(needsMethod);
        boolean hasOnRationaleParams = true;
        if (onRationale != null && onRationale.getParameters() != null) {
            hasOnRationaleParams = !onRationale.getParameters().isEmpty();
        }

        // getParameters 获取形参元素 是否存在参数
        boolean hasParameters = !needsMethod.getParameters().isEmpty();
        if (hasParameters) {
            if (hasOnRationaleParams) {
                // If the method has parameters, precede the potential OnRationale call with
                // an instantiation of the temporary Request object
                CodeBlock.Builder varargsCall =
                        CodeBlock.builder()
                                .add(
                                        "$N = new $N($N, ",
                                        pendingRequestFieldName(needsMethod),
                                        permissionRequestTypeName(rpe, needsMethod),
                                        targetParam)
                                .add(varargsParametersCodeBlock(needsMethod))
                                .addStatement(")");
                builder.addCode(varargsCall.build());
            } else {
                for (VariableElement it : needsMethod.getParameters()) {
                    CodeBlock.Builder varargsCall =
                            CodeBlock.builder()
                                    .addStatement("$N = $N", argumentFieldName(needsMethod, it), it.getSimpleName());
                    builder.addCode(varargsCall.build());
                }
            }
        }

        if (onRationale != null) {
            addShouldShowRequestPermissionRationaleCondition(builder, targetParam, permissionField);
            if (hasParameters) {
                if (hasOnRationaleParams) {
                    // For methods with parameters, use the PermissionRequest instantiated above
                    builder.addStatement(
                            "$N.$N($N)", targetParam, simpleString(onRationale), pendingRequestFieldName(needsMethod));
                } else {
                    builder.addStatement("$N.$N()", targetParam, simpleString(onRationale));
                }
            } else {
                if (hasOnRationaleParams) {
                    // Otherwise, create a new PermissionRequest on-the-fly
                    builder.addStatement(
                            "$N.$N(new $N($N))",
                            targetParam,
                            simpleString(onRationale),
                            permissionRequestTypeName(rpe, needsMethod),
                            targetParam);
                } else {
                    builder.addStatement("$N.$N()", targetParam, simpleString(onRationale));
                }
            }
            builder.nextControlFlow("else");
        }

        // Add the branch for "request permission"

        SensitivePermissionInterface var100000 = this.addWithCheckBodyMap.get(needsPermissionParameter);

        if (var100000 != null) {
            var100000.addRequestPermissionsStatement(builder, targetParam, activityVar, requestCodeField);
        } else {
            addRequestPermissionsStatement(builder, targetParam, permissionField, requestCodeField);
        }

        if (onRationale != null) {
            builder.endControlFlow();
        }
        builder.endControlFlow();
    }

    private List<MethodSpec> createPermissionHandlingMethods(RuntimePermissionsElement permissionsElement) {
        List<MethodSpec> list = new ArrayList<>();
        if (hasNormalPermission(permissionsElement)) {
            list.add(createPermissionResultMethod(permissionsElement));
        }

        return list;
    }

    private MethodSpec createOnActivityResultMethod(RuntimePermissionsElement permissionsElement) {
        String targetParam = "target";
        String requestCodeParam = "requestCode";
        String grantResultsParam = "grantResults";

        MethodSpec.Builder builder =
                MethodSpec.methodBuilder("onAbilityResult")
                        .addTypeVariables(permissionsElement.getTypeVariables())
                        .addModifiers(Modifier.STATIC)
                        .returns(TypeName.VOID)
                        .addParameter(ParameterSpec.builder(permissionsElement.getTypeName(), targetParam).build())
                        .addParameter(TypeName.INT, requestCodeParam);

        builder.beginControlFlow("switch($N)", requestCodeParam);
        for (ExecutableElement needsMethod : permissionsElement.getNeedsElements()) {
            String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
            if (!addWithCheckBodyMap.containsKey(needsPermissionParameter)) {
                continue;
            }
            builder.addCode("case $N : \n", requestCodeFieldName(needsMethod));
            addResultCaseBody(builder, needsMethod, permissionsElement, targetParam, grantResultsParam);
        }

        builder.addCode("default:\n").addStatement("break").endControlFlow();

        return builder.build();
    }

    private MethodSpec createPermissionResultMethod(RuntimePermissionsElement permissionsElement) {
        String targetParam = "target";
        String requestCodeParam = "requestCode";
        String grantResultsParam = "grantResults";
        MethodSpec.Builder builder =
                MethodSpec.methodBuilder("onRequestPermissionsFromUserResult")
                        .addTypeVariables(permissionsElement.getTypeVariables())
                        .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addParameter(ParameterSpec.builder(permissionsElement.getTypeName(), targetParam).build())
                        .addParameter(TypeName.INT, requestCodeParam)
                        .addParameter(ArrayTypeName.of(TypeName.INT), grantResultsParam);

        builder.beginControlFlow("switch ($N)", requestCodeParam);

        for (ExecutableElement needsMethod : permissionsElement.getNeedsElements()) {
            String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
            if (addWithCheckBodyMap.containsKey(needsPermissionParameter)) {
                continue;
            }

            builder.addCode("case $N: \n", requestCodeFieldName(needsMethod));
            addResultCaseBody(builder, needsMethod, permissionsElement, targetParam, grantResultsParam);
        }

        builder.addCode("default:\n").addStatement("break").endControlFlow();

        return builder.build();
    }

    private void addResultCaseBody(
            MethodSpec.Builder builder,
            ExecutableElement needsMethod,
            RuntimePermissionsElement rpe,
            String targetParam,
            String grantResultsParam) {
        ExecutableElement onDenied = rpe.findOnDeniedForNeeds(needsMethod);
        boolean hasDenied = onDenied != null;
        String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
        String permissionField = permissionFieldName(needsMethod);

        // Add the conditional for "permission verified"
        SensitivePermissionInterface var10000 = this.addWithCheckBodyMap.get(needsPermissionParameter);
        if (var10000 != null) {
            var10000.addHasSelfPermissionsCondition(builder, getActivityName(targetParam), permissionField);
        } else {
            builder.beginControlFlow("if ($T.verifyPermissions($N))", permissionUtils, grantResultsParam);
        }

        // Based on whether or not the method has parameters, delegate to the "pending request" object or invoke the
        // method directly
        ExecutableElement onRationale = rpe.findOnRationaleForNeeds(needsMethod);
        boolean hasOnRationaleParams = true;
        if (onRationale != null && onRationale.getParameters() != null) {
            hasOnRationaleParams = !onRationale.getParameters().isEmpty();
        }
        boolean hasParameters = !needsMethod.getParameters().isEmpty();
        if (hasParameters) {
            if (hasOnRationaleParams) {
                String pendingField = pendingRequestFieldName(needsMethod);
                builder.beginControlFlow("if ($N != null)", pendingField);
                builder.addStatement("$N.grant()", pendingField);
                builder.endControlFlow();
            } else {
                builder.addCode(
                        CodeBlock.builder()
                                .add("$N.$N(", targetParam, simpleString(needsMethod))
                                .add(varargsParametersCodeBlock(needsMethod, true))
                                .addStatement(")")
                                .build());
            }
        } else {
            builder.addStatement("target.$N()", simpleString(needsMethod));
        }

        // Add the conditional for "permission denied" and/or "never ask again", if present
        ExecutableElement onNeverAsk = rpe.findOnNeverAskForNeeds(needsMethod);
        boolean hasNeverAsk = onNeverAsk != null;

        if (hasDenied || hasNeverAsk) {
            builder.nextControlFlow("else");
        }
        if (hasNeverAsk) {
            // Split up the "else" case with another if condition checking for "never ask again" first
            addShouldShowRequestPermissionRationaleCondition(
                    builder, targetParam, permissionFieldName(needsMethod), false);
            builder.addStatement("target.$N()", simpleString(onNeverAsk));
            // If a "permission denied" is present as well, go into an else case, otherwise close this temporary branch
            if (hasDenied) {
                builder.nextControlFlow("else");
            } else {
                builder.endControlFlow();
            }
        }
        if (hasDenied) {
            // Add the "permissionDenied" statement
            builder.addStatement("$N.$N()", targetParam, simpleString(onDenied));
            // Close the additional control flow potentially opened by a "never ask again" method
            if (hasNeverAsk) {
                builder.endControlFlow();
            }
        }
        // Close the "switch" control flow
        builder.endControlFlow();

        // Remove the temporary pending request field, in case it was used for a method with parameters
        if (hasParameters) {
            if (hasOnRationaleParams) {
                builder.addStatement("$N = null", pendingRequestFieldName(needsMethod));
            } else {
                for (VariableElement variableElement : needsMethod.getParameters()) {
                    if (!Helpers.typeNameOf(variableElement).isPrimitive()) {
                        builder.addStatement("$N = null", argumentFieldName(needsMethod, variableElement));
                    }
                }
            }
        }
        builder.addStatement("break");
    }

    private boolean hasNormalPermission(RuntimePermissionsElement permissionsElement) {
        // 排除掉后直接返回true
        return true;
    }

    private boolean isDefinePermission(RuntimePermissionsElement permissionsElement, String permissionName) {
        for (ExecutableElement needsMethod : permissionsElement.getNeedsElements()) {
            List<String> permissionValue = Extensions.permissionValue(needsMethod.getAnnotation(NeedsPermission.class));
            if (permissionValue.contains(permissionName)) {
                return true;
            }
        }

        return false;
    }

    private List<MethodSpec> createOnShowRationaleCallbackMethods(RuntimePermissionsElement permissionsElement) {
        List<MethodSpec> methods = new ArrayList<>();
        for (ExecutableElement needsMethod : permissionsElement.getNeedsElements()) {
            ExecutableElement rationale = permissionsElement.findOnRationaleForNeeds(needsMethod);
            boolean isRationaleParamsEmpty = false;
            if (rationale != null && rationale.getParameters() != null && rationale.getParameters().isEmpty()) {
                isRationaleParamsEmpty = true;
            }
            if (isRationaleParamsEmpty) {
                methods.add(createProceedPermissionRequestMethod(permissionsElement, needsMethod));
                ExecutableElement onDenied = permissionsElement.findOnDeniedForNeeds(needsMethod);
                if (onDenied != null) {
                    methods.add(createCancelPermissionRequestMethod(permissionsElement, onDenied, needsMethod));
                }
            }
        }

        return methods;
    }

    private MethodSpec createProceedPermissionRequestMethod(
            RuntimePermissionsElement permissionsElement, ExecutableElement needsMethod) {
        String targetParam = "target";
        MethodSpec.Builder builder =
                MethodSpec.methodBuilder(Helpers.proceedOnShowRationaleMethodName(needsMethod))
                        .addTypeVariables(permissionsElement.getTypeVariables())
                        .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addParameter(ParameterSpec.builder(permissionsElement.getTypeName(), targetParam).build());

        String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
        String activityVar = getActivityName(targetParam);
        String requestCodeField = requestCodeFieldName(needsMethod);

        SensitivePermissionInterface var10000 = this.addWithCheckBodyMap.get(needsPermissionParameter);
        if (var10000 != null) {
            var10000.addRequestPermissionsStatement(builder, targetParam, activityVar, requestCodeField);
        } else {
            addRequestPermissionsStatement(
                    builder, targetParam, permissionFieldName(needsMethod), requestCodeField);
        }
        return builder.build();
    }

    private MethodSpec createCancelPermissionRequestMethod(
            RuntimePermissionsElement permissionsElement, ExecutableElement onDenied, ExecutableElement needsMethod) {
        return MethodSpec.methodBuilder(Helpers.cancelOnShowRationaleMethodName(needsMethod))
                .addTypeVariables(permissionsElement.getTypeVariables())
                .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                .returns(TypeName.VOID)
                .addParameter(ParameterSpec.builder(permissionsElement.getTypeName(), "target").build())
                .addStatement("target.$N()", simpleString(onDenied))
                .build();
    }

    private List<TypeSpec> createPermissionRequestClasses(RuntimePermissionsElement permissionsElement) {
        List<TypeSpec> classes = new ArrayList<>();
        for (ExecutableElement needsMethod : permissionsElement.getNeedsElements()) {
            ExecutableElement onRationale = permissionsElement.findOnRationaleForNeeds(needsMethod);
            boolean hasOnRationaleParams = true;
            if (onRationale != null && onRationale.getParameters() != null) {
                hasOnRationaleParams = !onRationale.getParameters().isEmpty();
            }
            if (hasOnRationaleParams) {
                classes.add(createPermissionRequestClass(permissionsElement, needsMethod));
            }
        }
        return classes;
    }

    private TypeSpec createPermissionRequestClass(
            RuntimePermissionsElement permissionsElement, ExecutableElement needsMethod) {
        // Select the superinterface of the generated class
        // based on whether or not the annotated method has parameters
        boolean hasParameters = !needsMethod.getParameters().isEmpty();
        String superInterfaceName;
        if (hasParameters) {
            superInterfaceName = "GrantableRequest";
        } else {
            superInterfaceName = "PermissionRequest";
        }

        TypeName targetType = permissionsElement.getTypeName();

        TypeSpec.Builder builder =
                TypeSpec.classBuilder(permissionRequestTypeName(permissionsElement, needsMethod))
                        .addTypeVariables(permissionsElement.getTypeVariables())
                        .addSuperinterface(ClassName.get("com.permissions.dispatcher.annotation", superInterfaceName))
                        .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);

        // Add required fields to the target
        String weakFieldName = "weakTarget";

        ParameterizedTypeName weakFieldType =
                ParameterizedTypeName.get(ClassName.get("java.lang.ref", "WeakReference"), targetType);
        builder.addField(weakFieldType, weakFieldName, Modifier.PRIVATE, Modifier.FINAL);
        for (VariableElement it : needsMethod.getParameters()) {
            builder.addField(Helpers.typeNameOf(it), simpleString(it), Modifier.PRIVATE, Modifier.FINAL);
        }

        // Add constructor
        String targetParam = "target";
        MethodSpec.Builder constructorBuilder =
                MethodSpec.constructorBuilder()
                        .addModifiers(Modifier.PRIVATE)
                        .addParameter(ParameterSpec.builder(targetType, targetParam).build())
                        .addStatement("this.$L = new WeakReference<$T>($N)", weakFieldName, targetType, targetParam);

        for (VariableElement it : needsMethod.getParameters()) {
            String fieldName = simpleString(it);
            constructorBuilder
                    .addParameter(Helpers.typeNameOf(it), fieldName)
                    .addStatement("this.$L = $N", fieldName, fieldName);
        }
        builder.addMethod(constructorBuilder.build());

        // Add proceed() override
        MethodSpec.Builder proceedMethod =
                MethodSpec.methodBuilder("proceed")
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .returns(TypeName.VOID)
                        .addStatement("$T target = $N.get()", targetType, weakFieldName)
                        .addStatement("if (target == null) return");
        String requestCodeField = requestCodeFieldName(needsMethod);
        String needsPermissionParameter = needsMethod.getAnnotation(NeedsPermission.class).value()[0];
        SensitivePermissionInterface var10000 = this.addWithCheckBodyMap.get(needsPermissionParameter);
        if (var10000 != null) {
            var10000.addRequestPermissionsStatement(
                    proceedMethod, targetParam, getActivityName(targetParam), requestCodeField);
        } else {
            addRequestPermissionsStatement(
                    proceedMethod, targetParam, permissionFieldName(needsMethod), requestCodeField);
        }
        builder.addMethod(proceedMethod.build());

        // Add cancel() override method
        MethodSpec.Builder cancelMethod =
                MethodSpec.methodBuilder("cancel")
                        .addAnnotation(Override.class)
                        .addModifiers(Modifier.PUBLIC)
                        .returns(TypeName.VOID);
        ExecutableElement onDenied = permissionsElement.findOnDeniedForNeeds(needsMethod);
        if (onDenied != null) {
            cancelMethod
                    .addStatement("$T target = $N.get()", targetType, weakFieldName)
                    .addStatement("if (target == null) return")
                    .addStatement("target.$N()", simpleString(onDenied));
        }
        builder.addMethod(cancelMethod.build());

        // For classes with additional parameters, add a "grant()" method

        if (hasParameters) {
            MethodSpec.Builder grantMethod =
                    MethodSpec.methodBuilder("grant")
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(TypeName.VOID);
            grantMethod
                    .addStatement("$T target = $N.get()", targetType, weakFieldName)
                    .addStatement("if (target == null) return");

            // Compose the call to the permission-protected method;
            // since the number of parameters is variable, utilize the low-level CodeBlock here
            // to compose the method call and its parameters
            grantMethod.addCode(
                    CodeBlock.builder()
                            .add("target.$N(", simpleString(needsMethod))
                            .add(varargsParametersCodeBlock(needsMethod))
                            .addStatement(")")
                            .build());
            builder.addMethod(grantMethod.build());
        }
        return builder.build();
    }
}
